package org.ow2.asmdex;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.ow2.asmdex.instruction.InstructionFormat10T;
import org.ow2.asmdex.instruction.InstructionFormat11N;
import org.ow2.asmdex.instruction.InstructionFormat11X;
import org.ow2.asmdex.instruction.InstructionFormat12X;
import org.ow2.asmdex.instruction.InstructionFormat20T;
import org.ow2.asmdex.instruction.InstructionFormat21C;
import org.ow2.asmdex.instruction.InstructionFormat21H;
import org.ow2.asmdex.instruction.InstructionFormat21S;
import org.ow2.asmdex.instruction.InstructionFormat21T;
import org.ow2.asmdex.instruction.InstructionFormat22B;
import org.ow2.asmdex.instruction.InstructionFormat22C;
import org.ow2.asmdex.instruction.InstructionFormat22S;
import org.ow2.asmdex.instruction.InstructionFormat22T;
import org.ow2.asmdex.instruction.InstructionFormat22X;
import org.ow2.asmdex.instruction.InstructionFormat23X;
import org.ow2.asmdex.instruction.InstructionFormat30T;
import org.ow2.asmdex.instruction.InstructionFormat31C;
import org.ow2.asmdex.instruction.InstructionFormat31I;
import org.ow2.asmdex.instruction.InstructionFormat31T;
import org.ow2.asmdex.instruction.InstructionFormat32X;
import org.ow2.asmdex.instruction.InstructionFormat35C;
import org.ow2.asmdex.instruction.InstructionFormat3RC;
import org.ow2.asmdex.instruction.InstructionFormat51L;
import org.ow2.asmdex.lowLevelUtils.DexFileReader;
import org.ow2.asmdex.structureCommon.Label;
import org.ow2.asmdex.structureCommon.LocalVariable;
import org.ow2.asmdex.structureReader.ISwitchCase;
import org.ow2.asmdex.structureReader.PackedSwitch;
import org.ow2.asmdex.structureReader.SparseSwitch;
import org.ow2.asmdex.structureReader.TryCatch;

/**
 * Class made to read the code of one method of a Dex file, and make a visitor visits its instructions.
 * Also parses the debug_info structure to get complementary information (line numbers, local variables...).
 * <br/><br/>
 * Should only be used by ApplicationReader.
 * <br/><br/>
 * The NOP instructions are ignored if we reach any Pseudo Instruction such as Packed/Sparse Switch or
 * Fill Array Data, so that NOP don't stack up if we transform an already generated dex file. Meeting any
 * other instruction will stop this behavior.
 * 
 * @author Julien Névo
 */
public class MethodCodeReader {

	/**
	 * The Method Visitor.
	 */
	protected MethodVisitor methodVisitor;
	
	/**
	 * The reader of the Dex file.
	 */
	protected DexFileReader dexFile;
	
	/**
	 * Offset of the code of the method inside the Dex file. May be 0 if the method is abstract or native.
	 */
	protected int codeOffset;
	
	/**
	 * Indicates if the debug information must be skipped.
	 */
	protected boolean skipDebug;
	
    /**
     * Offset in the Dex file of the first opcode of the code of the current method.
     */
    protected int instructionsStartOffset;
    
    /**
     * Offset in the Dex file of the byte next to the last opcode/data of the code
     * of the method.
     */
    protected int instructionsEndOffset;
    
	/**
     * Map containing the labels located at the given offset (from the beginning
     * of the code of the method).
     */
    protected HashMap<Integer, Label> labels;
    
    /**
     * Map containing the Packed Switch Structures located at the given offset
     * (from the beginning of the code of the method). The offset points to the
     * packed-switch opcode, NOT the packed-switch format itself.
     */
    protected HashMap<Integer, PackedSwitch> packedSwitchStructures;

    /**
     * Map containing the Sparse Switch Structures located at the given offset
     * (from the beginning of the code of the method). The offset points to the
     * sparse-switch opcode, NOT the sparse-switch format itself.
     */
    protected HashMap<Integer, SparseSwitch> sparseSwitchStructures;
    
    /**
     * Map containing lists of the TryCatch Structures located at the given offset
     * (from the beginning of the code of the method). The offset points to the
     * Try.
     */
    protected HashMap<Integer, ArrayList<TryCatch>> listTryCatchStructures;
    
    /**
     * Map containing, for each local variable identified by its number (as
     * described in the debug_info_item), a list of Local Variables.
     * Why a List ? Because a register number can share one or more variables
     * (though it doesn't happen often).
     * Note that when a Local Variable is started, it is added to the end of the
     * list, and all the Restarts/Ends found will be added to this Variable.
     */
    protected HashMap<Integer, List<LocalVariable>> localVariableLists;
    
    /**
     * Map linking a register to a type if it holds an array. It is useful for the
     * NewArray/FillNewArray instructions. 
     */
    private HashMap<Integer, Integer> registerArrayType = new HashMap<Integer, Integer>();
    
    /*
     * The five "state machine" registers of the debug_info_item.
     */
    /**
     * register of debug state machine : the debug address
     */
    protected int debugAddress; 
    /**
     * register of debug state machine : the debug line
     */
    protected int debugLine;
    /**
     * register of debug state machine : the source file
     */
    protected String debugSourceFile;
    /**
     * register of debug state machine : the prologue end
     */
    protected boolean debugPrologueEnd;
    /**
     * register of debug state machine : the epilogue end
     */
    protected boolean debugEpilogueBegin;
    
    /**
     * Copy of the original debugLine. This is useful, because we make two passes, and need
     * to recover the original value for the second.
     */
    protected int debugLineAtInitialization;
    
    /**
     * Copy of the original debugCurrentOpcodeOffset. This is useful, because we make two
     * passes, and need to recover the original value for the second.
     */
    protected int debugCurrentOpcodeOffsetAtInitialization;
    
    /**
     * Offset on opcodes of the debug_info_item.
     */
    protected int debugCurrentOpcodeOffset;
    
    /**
     * Indicates if a new Debug Line is emitted.
     */
    protected boolean newDebugLineEmitted;
    
    /**
     * Last emitted line (DBG_ADVANCE_LINE not counted).
     */
    protected int debugEmittedLine;
	
    /**
     * Size in bytes of a try_item structure.
     */
    private static final int TRY_ITEM_STRUCTURE_SIZE = 8;
    
    /**
     * Indicates if the Pseudo Instructions at the end of the method has been reached.
     * If true, the NOPs encountered aren't thrown so as not to interpret them and stack them
     * when reading once again a generated file. However, it returns to false if others instructions are
     * encountered, though it shouldn't happen as we encode try/catch/data array structures at the end
     * of the Method. But some compilers may do otherwise.
     */
    private boolean hasReachedPseudoInstructions = false;
    
    
	/**
	 * Constructor of the MethodCodeReader.
	 * @param dexFile  the Dex file. 
	 * @param methodVisitor visitor to visit the instructions of the code of the method.
	 * @param codeOffset offset of the code of the method inside the Dex file. May be 0 if
	 * 		  the method is abstract or native.
	 * @param skipDebug indicates if the debug information must be skipped.
	 */
	public MethodCodeReader(DexFileReader dexFile, MethodVisitor methodVisitor,
			int codeOffset, boolean skipDebug) {
		this.dexFile = dexFile;
		this.methodVisitor = methodVisitor;
		this.codeOffset = codeOffset;
		this.skipDebug = skipDebug;
	}
	
	/**
	 * Visits the code of a method. Also needs to parse the debug_info structure to get
	 * complementary information (line number, local variables...).
	 */
	public void visitMethodCode() {
		
		if (codeOffset != 0) {	// 0 if abstract or native.
			
			dexFile.seek(codeOffset);
		
			int registerSize = dexFile.ushort();
			
			dexFile.skipShort(); // Skips incomingArgumentsSize.
			dexFile.skipShort(); // Skips outgoingArgumentsSize.
			int triesSize = dexFile.ushort();
			int debugInfoOffset = dexFile.uint();
			int instructionsSizeInUShort = dexFile.uint();
			
			instructionsStartOffset = dexFile.getPos(); 
			instructionsEndOffset = instructionsStartOffset + instructionsSizeInUShort * 2;

			labels = new HashMap<Integer, Label>();
			packedSwitchStructures = new HashMap<Integer, PackedSwitch>();
			sparseSwitchStructures = new HashMap<Integer, SparseSwitch>();
			listTryCatchStructures = new HashMap<Integer, ArrayList<TryCatch>>(triesSize);
			localVariableLists = new HashMap<Integer, List<LocalVariable>>();
			
			// Checks the debug_info_item.
			if (!skipDebug && (debugInfoOffset != 0)) {
				// Reads the debug parameters. Even if not needed, we read them to get
				// past to reach the debug code just after.
				dexFile.seek(debugInfoOffset);
				
				debugLineAtInitialization = dexFile.uleb128();
				int debugParametersSize = dexFile.uleb128();
				
				// Parses the Parameters, and visits them.
				if (debugParametersSize > 0) {
					String[] parameters = new String[debugParametersSize];
					for (int indexParameter = 0 ; indexParameter < debugParametersSize; indexParameter++) {
						int stringIndex = dexFile.uleb128_p1();
						String paramName;
						if (stringIndex == Opcodes.NO_INDEX_SIGNED) {
							paramName = "";
						} else {
							paramName = dexFile.getStringItemFromStringIndex(stringIndex);
						}
						parameters[indexParameter] = paramName;
					}
					methodVisitor.visitParameters(parameters);
				}
				
				debugCurrentOpcodeOffsetAtInitialization = dexFile.getPos();
			}
			
			methodVisitor.visitCode();
			
			// Contrary to ASM, we visit the Maxs right at the beginning of the code.
			methodVisitor.visitMaxs(registerSize, 0);
			
			// Before parsing the code, we skip it in order to reach the
			// try/catch handler just after, if there is one.
			if (triesSize != 0) {
				dexFile.seek(instructionsEndOffset);
				// Padding ?
				if ((instructionsSizeInUShort % 2) != 0) {
					dexFile.ushort();
				}
				parseTryItemsFormat(dexFile.getPos(), triesSize);
			}
			
			// Now we parse the code, in two passes.
			// First pass : we only parse the code in order to find the Labels
			// (from Goto, Switch...). Visitors are NOT called. So Adapters can't add/remove code either.
			parseCodeInstructions(methodVisitor, skipDebug, true);
			// Second pass : we parse the code and call all the visitors. Adapters may add/remove code.
			parseCodeInstructions(methodVisitor, skipDebug, false);
			
			// Visits the Local Variables.
			for (List<LocalVariable> localVariableList : localVariableLists.values()) {
				for (LocalVariable localVariable : localVariableList) {
					methodVisitor.visitLocalVariable(localVariable.getName(), localVariable.getType(),
							localVariable.getSignature(), localVariable.getStart(),
							localVariable.getEnds(), localVariable.getRestarts(),
							localVariable.getRegister());
				}
			}
		}
		
		// The visitEnd event is thrown by the calling method.
	}
	
	/**
     * Parses the instructions of the code of the current method, and makes the given visitor
     * visit it, unless the parsing is done only to get the Label offsets.
     * In the latter case, we build the Label table whenever the following instructions are
     * encountered : GOTO, GOTO/16, GOTO/32, PACKED-SWITCH, SPARSE-SWITCH, IF-test, IF-testz. 
     * The dex file position <i>is</i> modified.
     * @param instructionsStartOffset offset in the Dex file of the first opcode of the
     * code of the method.
     * @param instructionsEndOffset offset in the Dex file of the byte next to the last
     * opcode/data of the code of the method.
     * @param methodVisitor visitor to call when parsing the code of the method.
     * @param skipDebug indicates if the debug information must be skipped.
     * @param findLabelsOnly indicates if the parsing is only about getting the Label offsets.
     * In this case, no method from the visitor must be called.
     */
    private void parseCodeInstructions(MethodVisitor methodVisitor, boolean skipDebug, boolean findLabelsOnly) {
   	
    	hasReachedPseudoInstructions = false;
    	
    	// Initializes the debug values.
		debugCurrentOpcodeOffset = debugCurrentOpcodeOffsetAtInitialization; // Offset on opcodes of the debug_info_item.
		
	    debugAddress = 0; 
	    debugLine = debugLineAtInitialization;
	    debugEmittedLine = 0;
	    debugSourceFile = null;
	    debugPrologueEnd = false;
	    debugEpilogueBegin = false;
	    newDebugLineEmitted = false;
	    
    	// Parses all the instructions.
    	dexFile.seek(instructionsStartOffset);
		while (dexFile.getPos() < instructionsEndOffset) {
            int relativeOffset = dexFile.getPos() - instructionsStartOffset;
			if (!skipDebug && (debugCurrentOpcodeOffset >= 0)) {
				// Parses the Debug Information Item. This will work only if it has information
				// to display for the current offset. Note that we don't need to parse
				// this structure on the first pass (findLabelOnly).
			
				parseDebugInformationItem(methodVisitor, relativeOffset, findLabelsOnly);
			}
			// If a Label is present here, visit it. The label table must of course have
			// been built before (on the first pass).
			if (labels.containsKey(relativeOffset)) {
			    Label label = labels.get(relativeOffset);
			    // The line number is valid only if set in the last occurrence
			    // of the Debug Informations parsing (it implies !skipDebug).
			    if (newDebugLineEmitted) {
			        label.setLine(debugEmittedLine);							
			    }

			    if (!findLabelsOnly) {
			        methodVisitor.visitLabel(label);
			    }
			}

			// Visits the line number (previously parsed) AFTER we visited
			// the Label.
			if (newDebugLineEmitted) {
			    if (!findLabelsOnly) {
			        methodVisitor.visitLineNumber(debugEmittedLine,
			                getLabel(relativeOffset));
			    }
			    newDebugLineEmitted = false;
			}

			// If a Try/Catch block is here, visit it. The TryCatch table has been
			// built before on first pass.
			if (listTryCatchStructures.containsKey(relativeOffset)) {
			    ArrayList<TryCatch> listTryCatch = listTryCatchStructures.get(relativeOffset);
			    for (TryCatch tcs : listTryCatch) {
			        if (!findLabelsOnly) {
			            methodVisitor.visitTryCatchBlock(tcs.getStart(),
			                    tcs.getEnd(), tcs.getHandler(), tcs.getType());
			        }
			    }
			}
			
			// Now reads the bytecode itself.
			int fullOpcode = dexFile.ushort();
			int shortOpcode = fullOpcode & 0xff;
			int highOrderByte = (fullOpcode >> 8) & 0xff;

			// Finding a NOP or "special" instructions such as the special instructions
			// such as Switch structures and Fill Array Data.
			if (shortOpcode == 0x00) {
				// NOP or "special" instructions.
				switch (highOrderByte) {
				case 0x00: // NOP.
					if ((!findLabelsOnly) && (!hasReachedPseudoInstructions)) {
						// NOPs are ignored when we have reached the Pseudo Instructions area (at the end
						// of the methods. This prevents the stacking of NOPs when reading a file that we
						// have previously generated.
						methodVisitor.visitInsn(shortOpcode);
					}
					break;
				case 0x01: { // Packed-switch structure
					// Since the structure was already parsed (because we parse it
					// immediately when finding the packed-switch opcode), we skip it.
					int packedSwitchSize = dexFile.ushort();
					dexFile.seek(dexFile.getPos() + packedSwitchSize * 4 + 4);
					hasReachedPseudoInstructions = true;
					break;
				}
				case 0x02: { // Sparse-switch
					// Since the structure was already parsed (because we parse it
					// immediately when finding the packed-switch opcode), we skip it.
					int sparseSwitchSize = dexFile.ushort();
					dexFile.seek(dexFile.getPos() + sparseSwitchSize * 4 * 2);
					hasReachedPseudoInstructions = true;
					break;
				}
				case 0x03: { // Fill Array Data
					// Since the structure was already parsed (because we parse it
					// immediately when finding the fill-array-data opcode), we skip it.
					int elementWidth = dexFile.ushort();
					int elementSize = dexFile.uint();
					int totalSizeInBytes = elementWidth * elementSize;
					int codeUnitNumber = ((totalSizeInBytes + 1) / 2);
					dexFile.seek(dexFile.getPos() + codeUnitNumber * 2);
					hasReachedPseudoInstructions = true;
					break;
				}
				default :
					throw new RuntimeException("Unknown opcode after a 0x00 : 0x" + Integer.toHexString(highOrderByte) + " at " + Integer.toHexString(dexFile.getPos() - 2)); 
				}
			} else {
				// Other instructions than NOP are found. We thus consider we are not in a
				// "Pseudo Instructions" area, or have left one.
				hasReachedPseudoInstructions = false;
				switch (shortOpcode) {
				case 0x1: // 12x format.
				case 0x4:
				case 0x7: {
					if (findLabelsOnly) {
						// InstructionFormat12X.skip(dexFile);
					} else { 
						int regA = InstructionFormat12X.getRegisterA(fullOpcode);
						int regB = InstructionFormat12X.getRegisterB(fullOpcode);
						visitVarInsn(shortOpcode, methodVisitor, regA, regB);
					}
					break;
				}
				case 0x2: // 22x format.
				case 0x5:
				case 0x8: {
					if (findLabelsOnly) {
						InstructionFormat22X.skip(dexFile);
					} else {
						int regA = InstructionFormat22X.getRegisterA(fullOpcode);
						int regB = InstructionFormat22X.getRegisterB(dexFile);
						visitVarInsn(shortOpcode, methodVisitor, regA, regB);
					}
					break;
				}
				case 0x3: // 32x format.
				case 0x6:
				case 0x9: {
					if (findLabelsOnly) {
						InstructionFormat32X.skip(dexFile);
					} else {
						int regA = InstructionFormat32X.getRegisterA(dexFile);
						int regB = InstructionFormat32X.getRegisterB(dexFile);
						visitVarInsn(shortOpcode, methodVisitor, regA, regB);
					}
					break;
				}
				case 0xa: // 11x format.
				case 0xb:
				case 0xc:
				case 0xd:
				case 0xf:
				case 0x10:
				case 0x11:
				case 0x1d:
				case 0x1e:
				case 0x27: {
					if (findLabelsOnly) {
						// InstructionFormat11X.skip(dexFile);
					} else {
						int regA = InstructionFormat11X.getRegisterA(fullOpcode);
						methodVisitor.visitIntInsn(shortOpcode, regA);
					}
					break;
				}
				case 0xe: { // 10x format.
					if (!findLabelsOnly) methodVisitor.visitInsn(shortOpcode);
					break;
				}
				case 0x12: { // 11n format.
					if (findLabelsOnly) {
						// InstructionFormat11N.skip(dexFile);
					} else {
						int regA = InstructionFormat11N.getRegisterA(fullOpcode);
						int literalB = InstructionFormat11N.getLiteralB(fullOpcode);
						visitVarInsn(shortOpcode, methodVisitor, regA, literalB);
					}
					break;
				}
				case 0x13: // 21s format.
				case 0x16: {
					if (findLabelsOnly) {
						InstructionFormat21S.skip(dexFile);
					} else {
						int regA = InstructionFormat21S.getRegisterA(fullOpcode);
						int literalB = InstructionFormat21S.getLiteralB(dexFile);
						visitVarInsn(shortOpcode, methodVisitor, regA, literalB);
					}
					break;
				}
				case 0x14: // 31i format.
				case 0x17: {
					if (findLabelsOnly) {
						InstructionFormat31I.skip(dexFile);
					} else {
						int regA = InstructionFormat31I.getRegisterA(fullOpcode);
						int literalB = InstructionFormat31I.getLiteralB(dexFile);
						visitVarInsn(shortOpcode, methodVisitor, regA, literalB);
					}
					break;
				}
				case 0x15: { // 21h format.
					if (findLabelsOnly) {
						InstructionFormat21H.skip(dexFile);
					} else {
						int regA = InstructionFormat21H.getRegisterA(fullOpcode);
						int literalB = InstructionFormat21H.getLiteralB(dexFile);
						visitVarInsn(shortOpcode, methodVisitor, regA, literalB << 16);
					}
					break;
				}
				case 0x19: { // 21h format.
					if (findLabelsOnly) {
						InstructionFormat21H.skip(dexFile);
					} else {
						int regA = InstructionFormat21H.getRegisterA(fullOpcode);
						int literalB = InstructionFormat21H.getLiteralB(dexFile);
						visitVarInsn(shortOpcode, methodVisitor, regA, (long)literalB << 48);
					}
					break;
				}
				case 0x18: { // 51l format.
					if (findLabelsOnly) {
						InstructionFormat51L.skip(dexFile);
					} else {
						int regA = InstructionFormat51L.getRegisterA(fullOpcode);
						long literalB = InstructionFormat51L.getLiteralB(dexFile);
						methodVisitor.visitVarInsn(shortOpcode, regA, literalB);
					}
					break;
				}
				case 0x1a: { // 21c format.
					if (findLabelsOnly) {
						InstructionFormat21C.skip(dexFile);
					} else {
						int regA = InstructionFormat21C.getRegisterA(fullOpcode);
						int indexB = InstructionFormat21C.getIndexB(dexFile);
						String string = dexFile.getStringItemFromStringIndex(indexB);
						methodVisitor.visitStringInsn(shortOpcode, regA, string);
					}
					break;
				}
				case 0x1b: { // 31c format.
					if (findLabelsOnly) {
						InstructionFormat31C.skip(dexFile);
					} else {
						int regA = InstructionFormat31C.getRegisterA(fullOpcode);
						int indexB = InstructionFormat31C.getIndexB(dexFile);
						String string = dexFile.getStringItemFromStringIndex(indexB);
						methodVisitor.visitStringInsn(shortOpcode, regA, string);
					}
					break;
				}
				case 0x1c: { // 21c format. 
					if (findLabelsOnly) {
						InstructionFormat21C.skip(dexFile);
					} else {
						int regA = InstructionFormat21C.getRegisterA(fullOpcode);
						int indexB = InstructionFormat21C.getIndexB(dexFile);
						String typeName = dexFile.getStringItemFromTypeIndex(indexB);
						methodVisitor.visitTypeInsn(shortOpcode, regA, 0, 0, typeName);
					}
					break;
				}
				case 0x1f: { // 21c format.
					if (findLabelsOnly) {
						InstructionFormat21C.skip(dexFile);
					} else {
						int regA = InstructionFormat21C.getRegisterA(fullOpcode);
						int indexB = InstructionFormat21C.getIndexB(dexFile);
						String typeName = dexFile.getStringItemFromTypeIndex(indexB);
						methodVisitor.visitTypeInsn(shortOpcode, 0, regA, 0, typeName);
					}
					break;
				}
				case 0x20: { // 22c format.
					if (findLabelsOnly) {
						InstructionFormat22C.skip(dexFile);
					} else {
						int regA = InstructionFormat22C.getRegisterA(fullOpcode);
						int regB = InstructionFormat22C.getRegisterB(fullOpcode);
						int indexC = InstructionFormat22C.getIndexC(dexFile);
						String typeName = dexFile.getStringItemFromTypeIndex(indexC);
						methodVisitor.visitTypeInsn(shortOpcode, regA, regB, 0, typeName);
					}
					break;
				}
				case 0x21: { // 12x format.
					if (findLabelsOnly) {
						// InstructionFormat12X.skip(dexFile);
					} else {
						int regA = InstructionFormat12X.getRegisterA(fullOpcode);
						int regB = InstructionFormat12X.getRegisterB(fullOpcode);
						methodVisitor.visitArrayLengthInsn(regA, regB);
					}
					break;
				}
				case 0x22: { // 21c format.
					if (findLabelsOnly) {
						InstructionFormat21C.skip(dexFile);
					} else {
						int regA = InstructionFormat21C.getRegisterA(fullOpcode);
						int indexB = InstructionFormat21C.getIndexB(dexFile);
						String typeName = dexFile.getStringItemFromTypeIndex(indexB);
						methodVisitor.visitTypeInsn(shortOpcode, regA, 0, 0, typeName);
					}
					break;
				}
				case 0x23: { // 22c format.
					if (findLabelsOnly) {
						InstructionFormat22C.skip(dexFile);
					} else {
						int registerArray = InstructionFormat22C.getRegisterA(fullOpcode);
						int regB = InstructionFormat22C.getRegisterB(fullOpcode);
						int indexC = InstructionFormat22C.getIndexC(dexFile);
						String typeName = dexFile.getStringItemFromTypeIndex(indexC);
						methodVisitor.visitTypeInsn(shortOpcode, registerArray, 0, regB, typeName);
						
						// We have to store the type linked to the register used by the array declaration,
						// in case it is filled with a Fill Array Data instruction.
						int type = getTypeFromTypeArray(typeName);
						registerArrayType.put(registerArray, type);
					}
					break;
				}
				case 0x24: { // 35c format.
					if (findLabelsOnly) {
						InstructionFormat35C.skip(dexFile);
					} else {
						int index = InstructionFormat35C.getIndex(dexFile);
						int[] registers = InstructionFormat35C.getRegisters(dexFile, fullOpcode);
						visitMultiANewArrayInsn(methodVisitor, index, registers);
					}
					break;
				}
				case 0x25: { // 3rc format.
					if (findLabelsOnly) {
						InstructionFormat3RC.skip(dexFile);
					} else {
						int index = InstructionFormat3RC.getIndex(dexFile);
						int[] registers = InstructionFormat3RC.getRegisters(dexFile, fullOpcode);
						visitMultiANewArrayInsn(methodVisitor, index, registers);
					}
					break;
				}
				case 0x26: { // 31t format.
					if (findLabelsOnly) {
						InstructionFormat31T.skip(dexFile);
					} else {
						int regA = InstructionFormat31T.getRegisterA(fullOpcode);
						int offset = InstructionFormat31T.getOffset(dexFile, fullOpcode);
						visitFillArrayData(methodVisitor, regA, offset);
					}
					break;
				}
				case 0x28: { // 10t format.
					int offset = InstructionFormat10T.getOffset(dexFile, fullOpcode);
					visitJumpInsn(shortOpcode, offset, 0, 0, methodVisitor, findLabelsOnly);
					break;
				}
				case 0x29: { // 20t format.
					int offset = InstructionFormat20T.getOffset(dexFile, fullOpcode);
					visitJumpInsn(shortOpcode, offset, 0, 0, methodVisitor, findLabelsOnly);
					break;
				}
				case 0x2a: { // 30t format.
					int offset = InstructionFormat30T.getOffset(dexFile, fullOpcode);
					visitJumpInsn(shortOpcode, offset, 0, 0, methodVisitor, findLabelsOnly);
					break;
				}
				case 0x2b: // 31t format.
				case 0x2c: {
					parseAndVisitSwitchCase(methodVisitor, fullOpcode, findLabelsOnly);
					break;
				}
				case 0x2d: // 23x format.
				case 0x2e:
				case 0x2f:
				case 0x30:
				case 0x31: {
					if (findLabelsOnly) {
						InstructionFormat23X.skip(dexFile);
					} else {
						int regA = InstructionFormat23X.getRegisterA(fullOpcode);
						int regBAndC = InstructionFormat23X.getEncodedRegisterBAndC(dexFile);
						int regB = InstructionFormat23X.getRegisterBFromEncodedRegisterBAndC(regBAndC);
						int regC = InstructionFormat23X.getRegisterCFromEncodedRegisterBAndC(regBAndC);
						methodVisitor.visitOperationInsn(shortOpcode, regA, regB, regC, 0);
					}
					break;
				}
				case 0x32: // 22t format.
				case 0x33:
				case 0x34:
				case 0x35:
				case 0x36:
				case 0x37: {
					int regA = InstructionFormat22T.getRegisterA(fullOpcode);
					int regB = InstructionFormat22T.getRegisterB(fullOpcode);
					int offset = InstructionFormat22T.getOffset(dexFile, fullOpcode);
					visitJumpInsn(shortOpcode, offset, regA, regB, methodVisitor, findLabelsOnly);
					break;
				}
				case 0x38: // 21t format.
				case 0x39:
				case 0x3a:
				case 0x3b:
				case 0x3c:
				case 0x3d: {
					int regA = InstructionFormat21T.getRegisterA(fullOpcode);
					int offset = InstructionFormat21T.getOffset(dexFile, fullOpcode);
					visitJumpInsn(shortOpcode, offset, regA, 0, methodVisitor, findLabelsOnly);
					break;
				}
				case 0x44: // 23x format (Array Operations).
				case 0x45:
				case 0x46:
				case 0x47:
				case 0x48:
				case 0x49:
				case 0x4a:
				case 0x4b:
				case 0x4c:
				case 0x4d:
				case 0x4e:
				case 0x4f:
				case 0x50:
				case 0x51: {
					if (findLabelsOnly) {
						InstructionFormat23X.skip(dexFile);
					} else {
						int regA = InstructionFormat23X.getRegisterA(fullOpcode);
						int regBAndC = InstructionFormat23X.getEncodedRegisterBAndC(dexFile);
						int regB = InstructionFormat23X.getRegisterBFromEncodedRegisterBAndC(regBAndC);
						int regC = InstructionFormat23X.getRegisterCFromEncodedRegisterBAndC(regBAndC);
						methodVisitor.visitArrayOperationInsn(shortOpcode, regA, regB, regC);
					}
					break;
				}
				case 0x52: // 22c format.
				case 0x53:
				case 0x54:
				case 0x55:
				case 0x56:
				case 0x57:
				case 0x58:
				case 0x59:
				case 0x5a:
				case 0x5b:
				case 0x5c:
				case 0x5d:
				case 0x5e:
				case 0x5f: {
					if (findLabelsOnly) {
						InstructionFormat22C.skip(dexFile);
					} else {
						int regA = InstructionFormat22C.getRegisterA(fullOpcode);
						int regB = InstructionFormat22C.getRegisterB(fullOpcode);
						int indexC = InstructionFormat22C.getIndexC(dexFile);
						visitFieldInsn(methodVisitor, shortOpcode, regA, regB, indexC);
					}
					break;
				}
				case 0x60: // 21c format.
				case 0x61:
				case 0x62:
				case 0x63:
				case 0x64:
				case 0x65:
				case 0x66:
				case 0x67:
				case 0x68:
				case 0x69:
				case 0x6a:
				case 0x6b:
				case 0x6c:
				case 0x6d: {
					if (findLabelsOnly) {
						InstructionFormat21C.skip(dexFile);
					} else {
						int regA = InstructionFormat21C.getRegisterA(fullOpcode);
						int indexC = InstructionFormat21C.getIndexB(dexFile);
						visitFieldInsn(methodVisitor, shortOpcode, regA, 0, indexC);
					}
					break;
				}
				case 0x6e:
				case 0x6f:
				case 0x70:
				case 0x71:
				case 0x72: {
					if (findLabelsOnly) {
						InstructionFormat35C.skip(dexFile);
					} else {
						int index = InstructionFormat35C.getIndex(dexFile);
						int[] registers = InstructionFormat35C.getRegisters(dexFile, fullOpcode);
						visitMethodInstruction(methodVisitor, shortOpcode, index, registers);
					}
					break;
				}
				case 0x74:
				case 0x75:
				case 0x76:
				case 0x77:
				case 0x78: {
					if (findLabelsOnly) {
						InstructionFormat3RC.skip(dexFile);
					} else {
						int index = InstructionFormat3RC.getIndex(dexFile);
						int[] registers = InstructionFormat3RC.getRegisters(dexFile, fullOpcode);
						visitMethodInstruction(methodVisitor, shortOpcode, index, registers);
					}
					break;
				}
				case 0x7b:
				case 0x7c:
				case 0x7d:
				case 0x7e:
				case 0x7f:
				case 0x80:
				case 0x81:
				case 0x82:
				case 0x83:
				case 0x84:
				case 0x85:
				case 0x86:
				case 0x87:
				case 0x88:
				case 0x89:
				case 0x8a:
				case 0x8b:
				case 0x8c:
				case 0x8d:
				case 0x8e:
				case 0x8f: {
					if (findLabelsOnly) {
						// InstructionFormat12X.skip(dexFile);
					} else {
						int regA = InstructionFormat12X.getRegisterA(fullOpcode);
						int regB = InstructionFormat12X.getRegisterB(fullOpcode);
						methodVisitor.visitOperationInsn(shortOpcode, regA, regB, 0, 0);
					}
					break;
				}
				case 0x90:
				case 0x91:
				case 0x92:
				case 0x93:
				case 0x94:
				case 0x95:
				case 0x96:
				case 0x97:
				case 0x98:
				case 0x99:
				case 0x9a:
				case 0x9b:
				case 0x9c:
				case 0x9d:
				case 0x9e:
				case 0x9f:
				case 0xa0:
				case 0xa1:
				case 0xa2:
				case 0xa3:
				case 0xa4:
				case 0xa5:
				case 0xa6:
				case 0xa7:
				case 0xa8:
				case 0xa9:
				case 0xaa:
				case 0xab:
				case 0xac:
				case 0xad:
				case 0xae:
				case 0xaf: {
					if (findLabelsOnly) {
						InstructionFormat23X.skip(dexFile);
					} else {
						int regA = InstructionFormat23X.getRegisterA(fullOpcode);
						int regBAndC = InstructionFormat23X.getEncodedRegisterBAndC(dexFile);
						int regB = InstructionFormat23X.getRegisterBFromEncodedRegisterBAndC(regBAndC);
						int regC = InstructionFormat23X.getRegisterCFromEncodedRegisterBAndC(regBAndC);
						methodVisitor.visitOperationInsn(shortOpcode, regA, regB, regC, 0);
					}
					break;
				}
				case 0xb0:
				case 0xb1:
				case 0xb2:
				case 0xb3:
				case 0xb4:
				case 0xb5:
				case 0xb6:
				case 0xb7:
				case 0xb8:
				case 0xb9:
				case 0xba:
				case 0xbb:
				case 0xbc:
				case 0xbd:
				case 0xbe:
				case 0xbf:
				case 0xc0:
				case 0xc1:
				case 0xc2:
				case 0xc3:
				case 0xc4:
				case 0xc5:
				case 0xc6:
				case 0xc7:
				case 0xc8:
				case 0xc9:
				case 0xca:
				case 0xcb:
				case 0xcc:
				case 0xcd:
				case 0xce:
				case 0xcf: {
					if (findLabelsOnly) {
						// InstructionFormat12X.skip(dexFile);
					} else {
						int regA = InstructionFormat12X.getRegisterA(fullOpcode);
						int regB = InstructionFormat12X.getRegisterB(fullOpcode);
						methodVisitor.visitOperationInsn(shortOpcode, regA, regA, regB, 0);
					}
					break;
				}
				case 0xd0: // 22s format.
				case 0xd1:
				case 0xd2:
				case 0xd3:
				case 0xd4:
				case 0xd5:
				case 0xd6:
				case 0xd7: {
					if (findLabelsOnly) {
						InstructionFormat22S.skip(dexFile);
					} else {
						int regA = InstructionFormat22S.getRegisterA(fullOpcode);
						int regB = InstructionFormat22S.getRegisterB(fullOpcode);
						int literalC = InstructionFormat22S.getLiteralC(dexFile);
						methodVisitor.visitOperationInsn(shortOpcode, regA, regB, 0, literalC);
					}
					break;
				}
			 	case 0xd8: // 22b format.
				case 0xd9:
				case 0xda:
				case 0xdb:
				case 0xdc:
				case 0xdd:
				case 0xde:
				case 0xdf:
				case 0xe0:
				case 0xe1:
				case 0xe2: {
					if (findLabelsOnly) {
						InstructionFormat22B.skip(dexFile);
					} else {
						int regA = InstructionFormat22B.getRegisterA(fullOpcode);
						int regBAndLitC = InstructionFormat22B.getEncodedRegisterBAndLiteralC(dexFile);
						int regB = InstructionFormat22B.getRegisterBFromEncodedRegisterBAndC(regBAndLitC);
						int literalC = InstructionFormat22B.getLiteralCFromEncodedRegisterBAndLiteralC(regBAndLitC);
						methodVisitor.visitOperationInsn(shortOpcode, regA, regB, 0, literalC);
					}
					break;
				}
				default:
					// All other opcodes are considered unused.
					if (!findLabelsOnly) methodVisitor.visitInsn(shortOpcode);
					break;
						
				}
			}
		}
    }
	
	
	

    /**
     * Converts an array descriptor to a value (such as Opcodes.VALUE_INT). Only primitive types
     * can be converted, other types such as reference will return -1. This is like that because
     * only primitive can be used for the Fill Array Data instruction.
     * @param typeName the type of the array. Should be in the form "[X]", where X is a primitive
     * 	      descriptor.
     * @return the value of elements contained in the array (such as {@link Opcodes#VALUE_INT}), or -1.
     */
    private static int getTypeFromTypeArray(String typeName) {
    	int result = -1;
    	
    	if ((typeName.length() == 2) && (typeName.startsWith("["))) {
    		char t = typeName.charAt(1);
    		switch (t) {
    		case 'Z' :
    			result = Opcodes.VALUE_BOOLEAN;
    			break;
    		case 'B' :
				result = Opcodes.VALUE_BYTE;
				break;
    		case 'S' :
				result = Opcodes.VALUE_SHORT;
				break;
    		case 'C' :
				result = Opcodes.VALUE_CHAR;
				break;
    		case 'I' :
				result = Opcodes.VALUE_INT;
				break;
    		case 'J' :
				result = Opcodes.VALUE_LONG;
				break;
    		case 'F' :
				result = Opcodes.VALUE_FLOAT;
				break;
    		case 'D' :
				result = Opcodes.VALUE_DOUBLE;
				break;
    		}
    	}
    	
		return result;
	}




	/**
     * Parses the debug_info_item information of the given debug opcode. We stop the
     * reading when the debugAddress doesn't coincide with the bytecode pointer.
     * Updates the debugAddress and debugCurrentOpcodeOffset. However, the visit
     * isn't done here, but by the calling method.
     * The dex file reader position <i>is</i> saved.
     * @param methodVisitor the visitor to visit the debug information item.
     * @param currentBytecodeOffset Offset <i>from the method opcodes</i>
     * currently being parsed.
     * @param findLabelsOnly true if no visitor must be called, because we only want to set up
     * the Labels by parsing this structure.
     */
    protected void parseDebugInformationItem(MethodVisitor methodVisitor,
    		int currentBytecodeOffset, boolean findLabelsOnly) {
    	int saveReaderPosition = dexFile.getPos();
    	dexFile.seek(debugCurrentOpcodeOffset);
    	newDebugLineEmitted = false;

    	int nextDebugAddress;
    	
		boolean debugParsingContinue = true;
		while (debugParsingContinue) {
	    	int debugOpcode = dexFile.ubyte();
			switch (debugOpcode) {
			case Opcodes.DBG_END_SEQUENCE:
				debugParsingContinue = false;
				break;
			case Opcodes.DBG_ADVANCE_PC:
				debugAddress += dexFile.uleb128();
				break;
			case Opcodes.DBG_ADVANCE_LINE:
				debugLine += dexFile.sleb128();
				break;
			case Opcodes.DBG_START_LOCAL: {
				// Makes sure the variable is read at the right address.
				if ((debugAddress * 2) != currentBytecodeOffset) {
					debugParsingContinue = false;
				} else {
					parseDebugStartLocalVariable(methodVisitor, currentBytecodeOffset, false,
							findLabelsOnly);
				}
				break;
			}
			case Opcodes.DBG_START_LOCAL_EXTENDED: {
				// Makes sure the variable is read at the right address.
				if ((debugAddress * 2) != currentBytecodeOffset) {
					debugParsingContinue = false;
				} else {
					parseDebugStartLocalVariable(methodVisitor, currentBytecodeOffset, true,
							findLabelsOnly);
				}
				break;
			}
			case Opcodes.DBG_END_LOCAL: {
				// Makes sure the variable is read at the right address.
				if ((debugAddress * 2) != currentBytecodeOffset) {
					debugParsingContinue = false;
				} else {
					// Adds the END_LOCAL information to the local variable structure. It should
					// exist !
					int registerNumber = dexFile.uleb128();
					if (findLabelsOnly) {
						Label endLabel = createAndAddLabel(debugAddress * 2);
						addEndLabelToLocalVariableLabels(registerNumber, endLabel);
					}
				}
				break;
			}
			case Opcodes.DBG_RESTART_LOCAL: {
				// Makes sure the variable is read at the right address.
				if ((debugAddress * 2) != currentBytecodeOffset) {
					debugParsingContinue = false;
				} else {
					// Adds the RESTART_LOCAL information to the local variable structure. It
					// should exist !
					int registerNumber = dexFile.uleb128();
					if (findLabelsOnly) {
						Label restartLabel = createAndAddLabel(debugAddress * 2);
						addRestartLabelToLocalVariableLabels(registerNumber, restartLabel);
					}
				}
				break;
			}
			case Opcodes.DBG_SET_PROLOGUE_END:
				debugPrologueEnd = true;
				break;
			case Opcodes.DBG_SET_EPILOGUE_BEGIN:
				debugEpilogueBegin = true;
				break;
			case Opcodes.DBG_SET_FILE: {
				int nameIndex = dexFile.uleb128_p1();
				if (nameIndex != Opcodes.NO_INDEX_SIGNED) {
					debugSourceFile = dexFile.getStringItemFromStringIndex(nameIndex);
				}
				break;
			}
			default:
				// Special Opcode (>=0xa).
				// We only emit a New Debug Line if the address it points to is the current one.
				// Else, we do NOT make the reader advance to the next opcode, so that we
				// check, next time, if the line number we were on is now become
				// the current one.

				int adjustedOpcode = debugOpcode - Opcodes.DBG_FIRST_SPECIAL;
				nextDebugAddress = debugAddress + adjustedOpcode / Opcodes.DBG_LINE_RANGE;
		    	// Note : the debugAddress must be multiplied by 2 when comparing,
		    	// because it is an offset of 16-bit units.
				if (currentBytecodeOffset != (nextDebugAddress * 2)) {
					debugParsingContinue = false;
				} else {
					debugEpilogueBegin = false;
					debugPrologueEnd = false;
					
					debugLine += Opcodes.DBG_LINE_BASE + (adjustedOpcode % Opcodes.DBG_LINE_RANGE);
					// We store this new emitted debug line so that a Label found can be linked
					// to this line.
					newDebugLineEmitted = true; 
					debugEmittedLine = debugLine;
					debugAddress = nextDebugAddress;
					
					// By default, if we find a line number, we add a Label, because in 
					// ASM line numbers always refer to a Label.
					createAndAddLabel(debugAddress * 2); // * 2 because offset of 16-bit units.
				}
				break;
			}
			
			// If the parsing stops, we don't move the reader after the debug instruction
			// that made it stops. We will parse the same instruction on the next iteration.
			if (debugParsingContinue) {
				debugCurrentOpcodeOffset = dexFile.getPos();
			}
		}

		dexFile.seek(saveReaderPosition);
    }
    
    /**
     * Parses a DBG_START_LOCAL or DBG_START_LOCAL_EXTENDED instruction, and visits
     * the local variables.
     * @param methodVisitor the visitor to visit the debug information item.
     * @param currentBytecodeOffset Offset <i>from the method opcodes</i> currently being parsed.
     * @param isLocalExtended true if the instruction concerns a LOCAL_EXTENDED variable. 
     * @param findLabelsOnly true if no visitor must be called, because we only want to set up
     * 		  the Labels by parsing this structure.
     */
    protected void parseDebugStartLocalVariable(MethodVisitor methodVisitor,
    		int currentBytecodeOffset, boolean isLocalExtended, boolean findLabelsOnly) {
    	int registerNumber = dexFile.uleb128();
		int variableNameIndex = dexFile.uleb128_p1();
		int variableTypeIndex = dexFile.uleb128_p1();
		int variableSignatureIndex = 0;
		if (isLocalExtended) {
			variableSignatureIndex = dexFile.uleb128_p1();
		}
		String variableName = (variableNameIndex == Opcodes.NO_INDEX_SIGNED) ?
				null : dexFile.getStringItemFromStringIndex(variableNameIndex);
		String variableType = (variableTypeIndex == Opcodes.NO_INDEX_SIGNED) ?
				null : dexFile.getStringItemFromTypeIndex(variableTypeIndex);
		String variableSignature = isLocalExtended ?  
			((variableSignatureIndex == Opcodes.NO_INDEX_SIGNED) ?
				null : dexFile.getStringItemFromStringIndex(variableSignatureIndex)):
					null;
			
		Label startLabel = createAndAddLabel(currentBytecodeOffset);
		
		// Creates the localVariableLabels structure for this variable, if first pass.
		// We always create a new Local Variable and adds it to the list. Some variable can share the
		// same register, but of different type, so they must be managed separately.
		if (findLabelsOnly) {
			LocalVariable localVariable = new LocalVariable(registerNumber, variableName,
					variableType, variableSignature, startLabel, null, null);
			addNewLocalVariable(registerNumber, localVariable);
		}
    }

	/**
     * Returns the LAST Local Variable used according to the given register number.
     * If it doesn't exist, it creates it.
     * @param registerNumber
     * @return a Local Variable, new or existing.
     */
    protected LocalVariable getLocalVariable(int registerNumber) {
    	LocalVariable localVariable;
    	if (localVariableLists.containsKey(registerNumber)) {
    		List<LocalVariable> list = localVariableLists.get(registerNumber);
    		localVariable = list.get(list.size() - 1); 
    	} else {
    		List<LocalVariable> list = new ArrayList<LocalVariable>(1);
    		localVariable = new LocalVariable(registerNumber);
    		
    		list.add(localVariable);
    		localVariableLists.put(registerNumber, list);
    	}
    	
    	return localVariable;
    }
    
    /**
     * Adds the Local Variable at the end of the list designed by the register number.
     * @param registerNumber the register number of the Local Variable.
     * @param localVariable the Local Variable to add.
     */
    protected void addNewLocalVariable(int registerNumber, LocalVariable localVariable) {
    	if (localVariableLists.containsKey(registerNumber)) {
    		List<LocalVariable> list = localVariableLists.get(registerNumber);
    		list.add(localVariable);
    	} else {
    		List<LocalVariable> list = new ArrayList<LocalVariable>(1);
    		list.add(localVariable);
    		localVariableLists.put(registerNumber, list);
    	}
	}

    /**
     * Parses the "tries" structure. It may contains several "try_item" items.
     * In order to get all the information needed, it will also parse the
     * encoded_catch_handler_list (only reaching the information needed).
     * The dex file reader position <i>is</i> modified.
     * @param tryItemOffset offset to the "tries" structure (which offset is given in 
     * the code_item structure), from the beginning of the file.
     * @param triesSize number of "try" structure.
     */
    protected void parseTryItemsFormat(int tryItemOffset, int triesSize) {
    	dexFile.seek(tryItemOffset);
    	
    	for (int tryItemIndex = 0; tryItemIndex < triesSize ; tryItemIndex++) {
	    	int startAddress = dexFile.uint() * 2; // * 2 because encoded in 16-bit units.
	    	int instructionCount = dexFile.ushort() * 2;
	    	int handlerOffset = dexFile.ushort();
	    	
	    	// Seek the handler information into the catch_handler_item located in the
	    	// catch_handler_list structure. The offset inside this structure is given
	    	// by the field handlerOffset.
	    	int saveReaderPositionInTryItem = dexFile.getPos();

	    	dexFile.seek(tryItemOffset + triesSize * TRY_ITEM_STRUCTURE_SIZE
	    			+ handlerOffset);

	    	int nbCatchTypesRead = dexFile.sleb128();
	    	boolean isCatchAll = (nbCatchTypesRead <= 0);
    		int nbCatchTypes = Math.abs(nbCatchTypesRead);
    		
    		// Read the encoded_type_addr_pair list.
    		// For each exception handled type, we create a TryCatch item that will
    		// be visited on the second pass.
    		for (int typeAddrPairIndex = 0; typeAddrPairIndex < nbCatchTypes; typeAddrPairIndex++) {
    			int typeIndex = dexFile.uleb128();
    			int addr = dexFile.uleb128() * 2; // * 2 because mesured in 16-bit units.

    			// Creates the Labels.
    			Label startLabel = createAndAddLabel(startAddress);
    			Label endLabel = createAndAddLabel(startAddress + instructionCount);
    			Label handlerLabel = createAndAddLabel(addr);

    			TryCatch tcs = new TryCatch(startLabel, endLabel, handlerLabel,
    					dexFile.getStringItemFromTypeIndex(typeIndex));
    			
    			// Adds this TryCatch structure to the map, so that we can call the
    			// visitTryCatchBlock when visiting the bytecode itself by comparing
    			// the current offset to the offsets of the tryCatchStructures, and
    			// see if one (or more) fits.
    			addTryCatchStructure(tcs, startAddress);
    		}
    		
    		// Manage Catch All, if any.
    		if (isCatchAll) {
    			int catchAllAddress = dexFile.uleb128() * 2; // * 2 because mesured in 16-bit units.
    			
    			Label startLabel = createAndAddLabel(startAddress);
    			Label endLabel = createAndAddLabel(startAddress + instructionCount);
    			Label handlerLabel = createAndAddLabel(catchAllAddress);

    			TryCatch tcs = new TryCatch(startLabel, endLabel, handlerLabel, null); // In ASM, type is Null for catch all.
    			
    			addTryCatchStructure(tcs, startAddress);
    		}
	    	
    		dexFile.seek(saveReaderPositionInTryItem);
    	}
    }
	
	
    /**
	 * Adds a new Label to the Label list, mapped according to its offset from the first
	 * byte of the code of the method. Doesn't add it if it already exists.
	 * @param offset offset from the first byte of the code of the method.
	 */
	protected void addLabel(int offset) {
		if (!labels.containsKey(offset)) {
			Label label = new Label();
			label.setOffset(offset);
			labels.put(offset, label);
		}
	}
	
	/**
	 * Convenient method to add an (already resolved) label to the Label list. Returns
	 * a reference to the actually labels here. This is useful is a label already exists
	 * here.
	 * @param label label to add.
	 * @return the label actually put. This is useful not to have two labels at the same
	 * location.
	 */
	protected Label addLabel(Label label) {
		int offset = label.getOffset();
		Label returnedLabel = label;
		if (labels.containsKey(offset)) {
			returnedLabel = labels.get(offset);
		} else {
			labels.put(offset, label);
		}
		return returnedLabel;
	}
	
	/**
	 * Convenient method to add (already resolved) labels to the Label list.
	 * @param labelList list of the Labels to add.
	 */
	protected void addLabels(Label[] labelList) {
		for (Label label : labelList) {
			addLabel(label);
		}
	}
	
	/**
	 * Convenient method to add a TryCatchStructure to the listTryCatchStructures. As it
	 * contains lists of TryCatchStructure, we have to make sure that a list already
	 * exists before adding the structure, else we have to create one.
	 * @param tcs TryCatchStructure to add.
	 * @param relativeOffset Offset to where the Try is in the bytecode, relative
	 * to the first byte of the code of the method.
	 */
	protected void addTryCatchStructure(TryCatch tcs, int relativeOffset) {
		ArrayList<TryCatch> list = listTryCatchStructures.get(relativeOffset);
		if (list != null) {
			list.add(tcs);
		} else {
			list = new ArrayList<TryCatch>(1);
			list.add(tcs);
			listTryCatchStructures.put(relativeOffset, list);
		}
	}
	
	/**
	 * Convenient method to get the Label related to the offset (relative to the first
	 * byte of the code of the method).
	 * @param offset offset of the Label, relative to the first byte of the code of the method.
	 * @return the Label related to the offset given, or Null if the Label isn't found.
	 */
	protected Label getLabel(int offset) {
		return labels.get(offset);
	}
	
	/**
	 * Creates and adds to the Labels list a new Label which offset (relative to the first
	 * byte of the code of the method) is given, but if a Label already exists here,
	 * returns this existing label.
	 * @param offset offset of the Label, relative to the first byte of the code of
	 * the method).
	 * @return A new Label, or the Label existing at this offset.
	 */
	protected Label createAndAddLabel(int offset) {
		Label returnedLabel;
		if (labels.containsKey(offset)) {
			returnedLabel = labels.get(offset);
		} else {
			returnedLabel = new Label();
			returnedLabel.setOffset(offset);
			labels.put(offset, returnedLabel);
		}
		
		return returnedLabel;
	}
	
	
	/**
	 * Visits a Var Instruction.
	 * @param opcodeByte 8-bit opcode of the instruction.
	 * @param methodVisitor visitor to call.
	 * @param destinationRegister the destination register.
	 * @param var the operand of the instruction to be visited. This operand is
     *        either a value or a source Register.
	 */
	protected void visitVarInsn(int opcodeByte, MethodVisitor methodVisitor, 
			int destinationRegister, int var) {
		methodVisitor.visitVarInsn(opcodeByte, destinationRegister, var);
	}
	
	/**
	 * Visits a Var Instruction.
	 * @param opcodeByte 8-bit opcode of the instruction.
	 * @param methodVisitor visitor to call.
	 * @param destinationRegister the destination register.
	 * @param var the operand of the instruction to be visited. This operand is
     *        either a value or a source Register.
	 */
	protected void visitVarInsn(int opcodeByte, MethodVisitor methodVisitor, 
			int destinationRegister, long var) {
		methodVisitor.visitVarInsn(opcodeByte, destinationRegister, var);
	}
	
	
	
	
	
	/**
	 * Method used by the methods that decodes the invoke-kind, in order to get the
	 * invoked method information and call the visitor.
	 * The dex file reader position <i>is</i> saved.
	 * @param methodVisitor visitor to call.
	 * @param opcodeByte 8-bit opcode of the instruction.
	 * @param methodIndex method index of the invoked method.
	 * @param registers list of the registers encoded to pass to the invoked method.
	 */
	protected void visitMethodInstruction(MethodVisitor methodVisitor, int opcodeByte,
			int methodIndex, int[] registers) {
		int savePosReader = dexFile.getPos();
		
		dexFile.seek(dexFile.getOffsetMethodIdItem(methodIndex));
		int classIndex = dexFile.ushort(); // Get name from class_idx.
		int protoIndex = dexFile.ushort();
		int nameIndex = dexFile.uint();
		String methodOwner = dexFile.getStringItemFromTypeIndex(classIndex);
		String methodDescriptor = dexFile.getDescriptorFromPrototypeIndex(protoIndex);
		String methodName = dexFile.getStringItemFromStringIndex(nameIndex);
		
		methodVisitor.visitMethodInsn(opcodeByte, methodOwner, methodName, methodDescriptor, registers);
		dexFile.seek(savePosReader);
	}
	
	/**
	 * Parses and visits a Jump Instruction (format 10X, 20X, 30X).
	 * @param opcode the 8-bit opcode of the Instruction.
	 * @param offset offset relative to the beginning of the Dex file, to go to.
	 * @param firstRegister first register to test, if any.
	 * @param secondRegister second register to test, if any.
	 * @param methodVisitor MethodVisitor to visit the Jump.
	 * @param findLabelsOnly indicates if the parsing is only about getting the Label offsets.
     * 		  In this case, no method from the visitor must be called.
	 */
	protected void visitJumpInsn(int opcode, int offset, int firstRegister,
			int secondRegister, MethodVisitor methodVisitor, boolean findLabelsOnly) {
		int relativeOffset = offset - instructionsStartOffset; 
		if (findLabelsOnly) {
			// Generate a Label, which offset is related to the current opcode.
			addLabel(relativeOffset);
		} else {
			methodVisitor.visitJumpInsn(opcode, getLabel(relativeOffset), firstRegister, secondRegister);
		}
	}
	
	/**
	 * Visits a given Field Instruction, and parses it.
	 * The dex file reader position <i>is</i> saved.
	 * @param methodVisitor MethodVisitor to visit the instruction.
	 * @param opcode Opcode of the instruction
	 * @param registerA First register
	 * @param registerB Second register
	 * @param index index of the field definition
	 */
	protected void visitFieldInsn(MethodVisitor methodVisitor, int opcode, int registerA, int registerB, int index) {
	
		int savePosReader = dexFile.getPos();
		
		dexFile.seek(dexFile.getOffsetFieldIdItem(index));
		int classIndex = dexFile.ushort(); // Get name from class_idx.
		int typeIndex = dexFile.ushort();
		int nameIndex = dexFile.uint();
		String fieldOwner = dexFile.getStringItemFromTypeIndex(classIndex);
		String fieldDescriptor = dexFile.getStringItemFromTypeIndex(typeIndex);
		String fieldName = dexFile.getStringItemFromStringIndex(nameIndex);
		
		methodVisitor.visitFieldInsn(opcode, fieldOwner, fieldName, fieldDescriptor, registerA, registerB);
		dexFile.seek(savePosReader);
	}	
	
	/**
	 * Visits a MultiANewArrayInsn Instruction.
	 * The dex file reader position <i>is</i> saved.
	 * @param methodVisitor MethodVisitor to visit the instruction.
	 * @param index the index of the descriptor of the Array.
	 * @param registers the registers containing the values of the Array.
	 */
	protected void visitMultiANewArrayInsn(MethodVisitor methodVisitor,	int index, int[] registers) {
		String desc = dexFile.getStringItemFromTypeIndex(index);
		methodVisitor.visitMultiANewArrayInsn(desc, registers);
	}

	/**
	 * Visits a FillArrayData instruction. Just like switch/case instruction, we directly
	 * get to the array data (encoded later) in order to parse them for this visitor.
	 * @param methodVisitor MethodVisitor to visit the instruction.
	 * @param register register holding the array.
	 * @param offset offset of the data structure of the Array.
	 */
	protected void visitFillArrayData(MethodVisitor methodVisitor, int register, int offset) {
		
		int saveReaderPosition = dexFile.getPos();

		// Gets the type of the element held by the register. It should have been read before.
		int type = -1;
		if (registerArrayType.containsKey(register)) {
			type = registerArrayType.get(register);
		}
		
		// Gets to the Array Data and parse it.
		dexFile.seek(offset);

		dexFile.skipShort(); // Skips the 0x300 opcode.
		if (type == -1 ) {
			switch(dexFile.sshort()) {
			case 1:
				type = Opcodes.VALUE_BYTE;
				break;
			case 2:
				type = Opcodes.VALUE_SHORT;
				break;
			case 4:
				type = Opcodes.VALUE_INT;
				break;
			case 8:
				type = Opcodes.VALUE_LONG;
				break;
			default:
			}
		} else {
			dexFile.skipShort(); // Skips Element Width.
		}
		int elementSize = dexFile.uint();

		// Fills the array according to its type.
		Object[] elements;
		switch (type) {
		case Opcodes.VALUE_BYTE: {
			elements = new Byte[elementSize];
			for (int i = 0; i < elementSize; i++) {
				elements[i] = Byte.valueOf(dexFile.sbyte());
			}
			break;
		}
		case Opcodes.VALUE_SHORT: {
			elements = new Short[elementSize];
			for (int i = 0; i < elementSize; i++) {
				elements[i] = Short.valueOf(dexFile.sshort());
			}
			break;
		}
		case Opcodes.VALUE_CHAR: {
			elements = new Character[elementSize];
			for (int i = 0; i < elementSize; i++) {
				elements[i] = Character.valueOf((char)dexFile.sshort());
			}
			break;
		}
		case Opcodes.VALUE_INT: {
			elements = new Integer[elementSize];
			for (int i = 0; i < elementSize; i++) {
				elements[i] = Integer.valueOf(dexFile.sint());
			}
			break;
		}
		case Opcodes.VALUE_FLOAT: {
			elements = new Float[elementSize];
			for (int i = 0; i < elementSize; i++) {
				elements[i] = Float.valueOf(Float.intBitsToFloat((int)dexFile.sizedLong(4 - 1)));
			}
			break;
		}
		case Opcodes.VALUE_DOUBLE: {
			elements = new Double[elementSize];
			for (int i = 0; i < elementSize; i++) {
				elements[i] = Double.valueOf(Double.longBitsToDouble(dexFile.sizedLong(8 - 1)));
			}
			break;
		}
		case Opcodes.VALUE_LONG: {
			elements = new Long[elementSize];
			for (int i = 0; i < elementSize; i++) {
				elements[i] = Long.valueOf(dexFile.sizedLong(8 - 1));
			}
			break;
		}
		case Opcodes.VALUE_BOOLEAN: {
			elements = new Boolean[elementSize];
			for (int i = 0; i < elementSize; i++) {
				elements[i] = Boolean.valueOf(dexFile.sbyte() != 0);
			}
			break;
		}
		default :
			throw new IllegalArgumentException("This type (" + type + ") can't be encoded in an Array of primitive.");
		}
		
		methodVisitor.visitFillArrayDataInsn(register, elements);
		
		dexFile.seek(saveReaderPosition);
	}
	
	
	/**
	 * Parses and visits a Switch Case instruction, whether it be a Packed or Sparse switch.
	 * Its nature is found according to the given opcode.
	 * The dex file reader must point after the 16-bit opcode. On return, points after
	 * the full instruction.
	 * @param methodVisitor MethodVisitor to visit the switch.
	 * @param fullOpcode the 16-bit opcode.
	 * @param findLabelsOnly indicates if the parsing is only about getting the Label offsets.
     * 		  In this case, no method from the visitor must be called.
	 */
	protected void parseAndVisitSwitchCase(MethodVisitor methodVisitor,
			int fullOpcode,	boolean findLabelsOnly) {
	
		boolean isPackedSwitch = ((fullOpcode & 0xff) == 0x2b);
		
		// Get the offset of the switch (just after the 16-bit opcode).
		int switchOffset = dexFile.getPos() - 2;
		int regA = InstructionFormat31T.getRegisterA(fullOpcode);
		int structureOffset = InstructionFormat31T.getOffset(dexFile, fullOpcode);
		
		if (findLabelsOnly) {
			int relativeStructureOffset = structureOffset - instructionsStartOffset;
			// Generates a Label to the structure, which offset is related
			// to the current opcode.
			addLabel(relativeStructureOffset);

			// Gets the Labels of the structure, and adds the structure for visiting it
			// later.
			int relativeSwitchOffset = switchOffset - instructionsStartOffset; 
			ISwitchCase cs = (isPackedSwitch ? 
					parsePackedSwitchFormat(relativeSwitchOffset, structureOffset) :
					parseSparseSwitchFormat(relativeSwitchOffset, structureOffset));
			
			Label[] switchLabels = cs.getSwitchLabels();
			addLabels(switchLabels);
			addLabel(cs.getDefaultLabel());
			if (isPackedSwitch) {
				packedSwitchStructures.put(switchOffset, (PackedSwitch)cs);
			} else {
				sparseSwitchStructures.put(switchOffset, (SparseSwitch)cs);
			}
		} else {
			// Visits the switch structure.
			// The structure SHOULD be here, as it has been put in the first pass.
			if (isPackedSwitch) {
				PackedSwitch pss = packedSwitchStructures.get(switchOffset);
				methodVisitor.visitTableSwitchInsn(regA, pss.getValueMin(), pss.getValueMax(),
					pss.getDefaultLabel(), pss.getSwitchLabels());
			} else {
				SparseSwitch sss = sparseSwitchStructures.get(switchOffset);
				methodVisitor.visitLookupSwitchInsn(regA, sss.getDefaultLabel(),
						sss.getKeys(), sss.getSwitchLabels());
			}
		}
	}
	
	
	/**
	 * Reads a packed-switch structure, from the switch offset and the packed switch
	 * offset and returns a structure containing all the information read.
	 * The dex file reader position <i>is</i> saved.
	 * @param relativeSwitchOffset offset of the first byte of the switch opcode, from
	 * 		  the beginning of the code of the method.
	 * @param packedSwitchOffset offset of the packed switch structure, from the
	 * 		  beginning of the Dex file.
	 * @return a PackedSwitchStructure containing all the information of the packed
	 * 		  switch structure.
	 */
	protected PackedSwitch parsePackedSwitchFormat(int relativeSwitchOffset, int packedSwitchOffset) {
		int savedReaderPosition = dexFile.getPos();

		PackedSwitch pss = new PackedSwitch();
		dexFile.seek(packedSwitchOffset);

		dexFile.skipShort(); // Skips identifying pseudo-code (0x100).
		int nbEntries = dexFile.ushort();
		int valueMin = dexFile.sint();

		// Reads the Label list.
		Label[] switchLabels = new Label[nbEntries];
		for (int entryIndex = 0; entryIndex < nbEntries; entryIndex++) {
			// * 2 because branch offsets are word based.
			int labelOffset = dexFile.sint() * 2 + relativeSwitchOffset;
			Label label = createAndAddLabel(labelOffset);
			switchLabels[entryIndex] = label;
		}
		
		pss.setSwitchLabels(switchLabels);
		// Default label is what follows the switch opcode.
		pss.setDefaultLabel(createAndAddLabel(relativeSwitchOffset + 6));
		pss.setValueMin(valueMin);
		pss.setValueMax(valueMin + nbEntries - 1);
		dexFile.seek(savedReaderPosition);
		return pss;
	}
	
	/**
	 * Reads a sparse-switch structure, from the switch offset and the packed switch
	 * offset and returns a structure containing all the information read.
	 * The dex file reader position <i>is</i> saved.
	 * @param relativeSwitchOffset offset of the first byte of the switch opcode, from
	 * the beginning of the code of the method.
	 * @param sparseSwitchOffset offset of the packed switch structure, from the
	 * beginning of the Dex file.
	 * @return a SparseSwitchStructure containing all the information of the sparse
	 * switch structure.
	 */
	protected SparseSwitch parseSparseSwitchFormat(int relativeSwitchOffset, int sparseSwitchOffset) {
		int savedReaderPosition = dexFile.getPos();

		SparseSwitch sss = new SparseSwitch();
		dexFile.seek(sparseSwitchOffset);

		dexFile.skipShort(); // Skips identifying pseudo-code (0x200).
		int nbEntries = dexFile.ushort();
		// Reads the Keys array.
		int[] keys = new int[nbEntries];
		for (int entryIndex = 0; entryIndex < nbEntries; entryIndex++) {
			keys[entryIndex] = dexFile.sint();
		}

		// Reads the Targets array.
		Label[] switchLabels = new Label[nbEntries];
		for (int entryIndex = 0; entryIndex < nbEntries; entryIndex++) {
			// * 2 because branch offsets are word based.
			int labelOffset = dexFile.sint() * 2 + relativeSwitchOffset;
			Label label = createAndAddLabel(labelOffset);
			switchLabels[entryIndex] = label;
		}
		
		sss.setSwitchLabels(switchLabels);
		sss.setKeys(keys);
		
		// Default label is what follows the switch opcode.
		sss.setDefaultLabel(createAndAddLabel(relativeSwitchOffset + 6));
		
		dexFile.seek(savedReaderPosition);
		return sss;
	}
	
	/**
	 * Adds an End Label to a Local Variable labels structure.
	 * @param registerNumber register number.
	 * @param endLabel the Label to add.
	 */
	protected void addEndLabelToLocalVariableLabels(int registerNumber, Label endLabel) {
		LocalVariable localVariable = getLocalVariable(registerNumber);
		localVariable.addEnd(endLabel);
	}
	
	/**
	 * Adds an Restart Label to a Local Variable labels structure.
	 * @param registerNumber register number.
	 * @param restartLabel the Label to add.
	 */
	protected void addRestartLabelToLocalVariableLabels(int registerNumber, Label restartLabel) {
		LocalVariable localVariable = getLocalVariable(registerNumber);
		localVariable.addRestart(restartLabel);
	}
	
}
